#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>

#include "mw_config.h"
#include "mw_core.h"
#include "mw_channel.h"
#include "mw_process.h"
#include "mw_process_cycle.h"

#include <glog/logging.h>


int mw_write_channel(int s, mw_channel_t *ch, size_t size)
{
    ssize_t             n;
    int                 err;
    struct iovec        iov[1];
    struct msghdr       msg;

    union {
        struct cmsghdr  cm;
        char            space[CMSG_SPACE(sizeof(int))];
    } cmsg;

    if (ch->fd == -1) {
        msg.msg_control = NULL;
        msg.msg_controllen = 0;
		LOG(WARNING)<<"ch->fd == -1";
    } else {
		// comment by zhuyangbo may some variant may uninitialized, some bug this branch
        msg.msg_control = (caddr_t) &cmsg;
        msg.msg_controllen = sizeof(cmsg);

        cmsg.cm.cmsg_len = CMSG_LEN(sizeof(int));
        cmsg.cm.cmsg_level = SOL_SOCKET;
        cmsg.cm.cmsg_type = SCM_RIGHTS;

        /*
         * We have to use_memcpy() instead of simple
         *   *(int *) CMSG_DATA(&cmsg.cm) = ch->fd;
         * because some gcc 4.4 with -O2/3/s optimization issues the warning:
         *   dereferencing type-punned pointer will break strict-aliasing rules
         *
         * Fortunately, gcc with -O1 compiles this memcpy()
         * in the same simple assignment as in the code above
         */

        memcpy(CMSG_DATA(&cmsg.cm), &ch->fd, sizeof(int));
    }

    msg.msg_flags = 0;

    iov[0].iov_base = (char *)ch;
    iov[0].iov_len = size;

    msg.msg_name = NULL;
    msg.msg_namelen = 0;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;

    n = sendmsg(s, &msg, 0);

    if (n == -1) {
        err = errno;
        if (err == EAGAIN) {
            return MW_AGAIN;
        }

        LOG(ERROR) << "sendmsg() failed (" << strerror(errno) << ")";
        return MW_ERROR;
    }

    return MW_OK;
}


int mw_read_channel(int s, mw_channel_t *ch, size_t size)
{
    ssize_t             n;
    int           err;
    struct iovec        iov[1];
    struct msghdr       msg;

    union {
        struct cmsghdr  cm;
        char            space[CMSG_SPACE(sizeof(int))];
    } cmsg;

    iov[0].iov_base = (char *) ch;
    iov[0].iov_len = size;

    msg.msg_name = NULL;
    msg.msg_namelen = 0;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;

    msg.msg_control = (caddr_t) &cmsg;
    msg.msg_controllen = sizeof(cmsg);

    n = recvmsg(s, &msg, 0);

    if (n == -1) {
        err = errno;
        if (err == EAGAIN) {
            return MW_AGAIN;
        }

        LOG(ERROR) << "recvmsg() failed (" << strerror(errno) << ")";
        return MW_ERROR;
    }

    if (n == 0) {
        LOG(ERROR) << "recvmsg() returned zero";
        return MW_ERROR;
    }

    if ((size_t) n < sizeof(mw_channel_t)) {
        LOG(ERROR) << "recvmsg() returnned not enough data: " << n;
        return MW_ERROR;
    }

    if (ch->command == MW_CMD_OPEN_CHANNEL) {

        if (cmsg.cm.cmsg_len < (socklen_t) CMSG_LEN(sizeof(int))) {
            LOG(ERROR) << "recvmsg() returned too small ancillary data";
            return MW_ERROR;
        }

        if (cmsg.cm.cmsg_level != SOL_SOCKET || cmsg.cm.cmsg_type != SCM_RIGHTS) {
            LOG(ERROR) << "recvmsg() returned invalid ancillary data level "
                << cmsg.cm.cmsg_level << " or type " << cmsg.cm.cmsg_type;
            return MW_ERROR;
        }

        /* ch->fd = *(int *) CMSG_DATA(&cmsg.cm); */

        memcpy(&ch->fd, CMSG_DATA(&cmsg.cm), sizeof(int));
    }

    if (msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) {
        LOG(ERROR) << "recvmsg() truncated data";
    }

    return n;
}

void mw_close_channel(int *fd)
{
    if (close(fd[0]) == -1) {
        LOG(ERROR) << "close() channel failed";
    }

    if (close(fd[1]) == -1) {
        LOG(ERROR) << "close() channel failed";
    }
}
